{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "DkA0Fobtb9dM"
   },
   "source": [
    "##### Copyright 2022 The Cirq Developers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "cellView": "form",
    "id": "tUshu7YfcAAW"
   },
   "outputs": [],
   "source": [
    "# @title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
    "# you may not use this file except in compliance with the License.\n",
    "# You may obtain a copy of the License at\n",
    "#\n",
    "# https://www.apache.org/licenses/LICENSE-2.0\n",
    "#\n",
    "# Unless required by applicable law or agreed to in writing, software\n",
    "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
    "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
    "# See the License for the specific language governing permissions and\n",
    "# limitations under the License."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "5v5kLs-DU9qp"
   },
   "source": [
    "# Quantum Virtual Engine"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "wRVl7p4zv8eT"
   },
   "source": [
    "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
    "  <td>\n",
    "    <a target=\"_blank\" href=\"https://quantumai.google/cirq/simulate/virtual_engine_interface\"><img src=\"https://quantumai.google/site-assets/images/buttons/quantumai_logo_1x.png\" />View on QuantumAI</a>\n",
    "  </td>\n",
    "  <td>\n",
    "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/quantumlib/Cirq/blob/main/docs/simulate/virtual_engine_interface.ipynb\"><img src=\"https://quantumai.google/site-assets/images/buttons/colab_logo_1x.png\" />Run in Google Colab</a>\n",
    "  </td>\n",
    "  <td>\n",
    "    <a target=\"_blank\" href=\"https://github.com/quantumlib/Cirq/blob/main/docs/simulate/virtual_engine_interface.ipynb\"><img src=\"https://quantumai.google/site-assets/images/buttons/github_logo_1x.png\" />View source on GitHub</a>\n",
    "  </td>\n",
    "  <td>\n",
    "    <a href=\"https://storage.googleapis.com/tensorflow_docs/Cirq/docs/simulate/virtual_engine_interface.ipynb\"><img src=\"https://quantumai.google/site-assets/images/buttons/download_icon_1x.png\" />Download notebook</a>\n",
    "  </td>\n",
    "</table>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "BCeleGCgmY4y"
   },
   "source": [
    "Cirq provides the [Quantum Virtual Machine](./quantum_virtual_machine.ipynb), which consists of two components: \n",
    "- The Quantum Virtual Engine: A class that implements the same interface as `cirq_google.Engine`, allowing you to simulate circuits with the same software interface that the real hardware uses. \n",
    "- Realistic noise models that mimic the behavior of real quantum hardware.\n",
    "\n",
    "This tutorial covers the former of the two components, the Quantum Virtual Engine, and how to run circuits on existing and custom virtual processor models."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "4Sif48OM1vPM"
   },
   "source": [
    "## Setup"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "AL_CcSjjmL8n"
   },
   "outputs": [],
   "source": [
    "try:\n",
    "    import cirq\n",
    "    import cirq_google\n",
    "except ImportError:\n",
    "    print(\"installing cirq...\")\n",
    "    !pip install --quiet cirq\n",
    "    print(\"installed cirq.\")\n",
    "    import cirq\n",
    "    import cirq_google\n",
    "\n",
    "import sympy"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "zKWtgtrYZIVy"
   },
   "source": [
    "Communication with real quantum hardware in Cirq is done through the `cirq_google.Engine` class. Each `Engine` can contain multiple quantum processors, and the `Engine` class provides functions to run circuits and manage jobs sent to those processors. The Virtual Engine in Cirq is an instance of the class `cirq_google.SimulatedLocalEngine` that runs circuits on the built-in Cirq [Simulator](./simulation.ipynb) instead of on hardware, but uses the same interface as `Engine`. This is useful for testing your circuit and code pipeline before running on actual hardware, and can be used as a substitute when the real hardware is not available.\n",
    "\n",
    "The interface implemented by both `cirq_google.Engine` and `cirq_google.SimulatedLocalEngine` is called `cirq_google.AbstractEngine`, and defines the various functions and types involved with using either option. When writing functions of your own, this interface enables you to seamlessly support simulated and real-hardware versions of the `Engine` interface. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "kuso8OxNo3t0"
   },
   "source": [
    "## Instantiate a virtual Engine\n",
    "\n",
    "The easiest way to create a `cirq_google.SimulatedLocalEngine` is to make one from one or more processor templates. \n",
    "Example processor device specifications can be found in \n",
    "the [devices/specifications](https://github.com/quantumlib/Cirq/tree/main/cirq-google/cirq_google/devices/specifications) folder of `cirq_google` in the Cirq GitHub repository.  These device specifications closely match previous versions of Google quantum hardware, and can serve as templates for processors in a `SimulatedLocalEngine`. When Google hardware becomes publicly available again in the future, it will have device specifications like these that differ in details, but not in format.\n",
    "\n",
    "You can create a `cirq_google.SimulatedLocalEngine` that includes these example device specifications using `cirq_google.engine.create_noiseless_virtual_engine_from_latest_templates()`.  For example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "dBbX5ll_blLI"
   },
   "outputs": [],
   "source": [
    "engine = cirq_google.engine.create_noiseless_virtual_engine_from_latest_templates()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "fwoJCTe0VPxl"
   },
   "source": [
    "You can then use this Engine object to perform operations as if it included real hardware.  However, all interactions will be local and mocked with these example processors.  Program execution will be done by the Cirq Simulator.\n",
    "\n",
    "For instance, you can list the processors and their device layouts, which are the same as those specified in the `devices/specification` folder:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "wF6ay4KPVPTL"
   },
   "outputs": [],
   "source": [
    "for proc in engine.list_processors():\n",
    "    print(proc.processor_id)\n",
    "    print('-----------------')\n",
    "    print(proc.get_device())\n",
    "    print('\\n\\n\\n')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "sXsTR4ZpXfka"
   },
   "source": [
    "## Run circuits and sweeps\n",
    "\n",
    "After creating the `SimulatedLocalEngine`, you can use any function that you might use with a normal `Engine` that has real quantum processors in it. Most importantly, this includes the ability to run circuits. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "xmEzIfseVPg4"
   },
   "outputs": [],
   "source": [
    "# Choose one of the (simulated) processors to run on.\n",
    "weber = engine.get_processor('weber')\n",
    "sampler = weber.get_sampler()\n",
    "\n",
    "# Run a simple circuit for ten repetitions\n",
    "result = sampler.run(cirq.Circuit(cirq.measure(cirq.GridQubit(7, 2))), repetitions=10)\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "H8aIHqEBYDnr"
   },
   "source": [
    "Note that, even though this is a simulated processor device, there are still device constraints that must be met by a circit in order for it to be executed. For example, the gates and qubits used by the circuit must be supported by the device:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "Ky1fiTLPYIqM"
   },
   "outputs": [],
   "source": [
    "# Weber does not have a (7, 1) qubit.\n",
    "try:\n",
    "    sampler.run(cirq.Circuit(cirq.measure(cirq.GridQubit(7, 1))), repetitions=10)\n",
    "except ValueError as e:\n",
    "    print(e)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "FtCpP5evY2Ss"
   },
   "source": [
    "You can also run [Parameter Sweeps](./params.ipynb) with the `run_sweep` function, which returns a `cirq.Job`-type object instead of a `Result`. This way, jobs can be prepared and run asynchronously. When running a parameter sweep over many parameter options, or with particularly large circuits, it can be useful to set the job running and return for the results later, with the ability to check job execution status in between. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "0VECfWL0Y91w"
   },
   "outputs": [],
   "source": [
    "qubit = cirq.GridQubit(7, 2)\n",
    "circuit = cirq.Circuit(cirq.X(qubit) ** sympy.Symbol('t'), cirq.measure(qubit, key='m'))\n",
    "job = weber.run_sweep(circuit, params=cirq.Linspace('t', 0, 2, 20), repetitions=1000)\n",
    "print(f'job is type {type(job)}')\n",
    "print(f'job has id {job.id()} and status {job.execution_status()}')\n",
    "print('')\n",
    "\n",
    "print('Now executing results!')\n",
    "results = job.results()\n",
    "print('')\n",
    "print(f'job has id {job.id()} and status {job.execution_status()}')\n",
    "\n",
    "print('')\n",
    "print('Results:')\n",
    "for result in results:\n",
    "    print(result.histogram(key='m'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "tRaVuulAbYat"
   },
   "source": [
    "## Reservations and scheduling\n",
    "\n",
    "Other functions are available to `Engine` classes that are part of using the `Engine` as a service. These include reservations, scheduling, downtime, and others. These functions are also available with the virtual processors, though all of them will generally succeed since there are no other users using the virtual service."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "Qc_zkuzdOCVn"
   },
   "outputs": [],
   "source": [
    "print(f'Next expected downtime: {weber.expected_down_time()}')\n",
    "print(f'Next expected recovery: {weber.expected_recovery_time()}')\n",
    "\n",
    "# Creating two example reservations\n",
    "import datetime\n",
    "\n",
    "now = datetime.datetime.now()\n",
    "hour = datetime.timedelta(hours=1)\n",
    "try:\n",
    "    weber.create_reservation(start_time=now, end_time=now + hour)\n",
    "    weber.create_reservation(\n",
    "        start_time=now + 2 * hour,\n",
    "        end_time=now + 3 * hour,\n",
    "        allowlisted_users=['mysterious_fake_user@nonexistentwebsite.domain'],\n",
    "    )\n",
    "except ValueError as e:\n",
    "    # If you re-run this cell, it will note that you already have a reservation\n",
    "    print('Cannot reserve time, did you already reserve it?  Error:')\n",
    "    print(e)\n",
    "\n",
    "print('')\n",
    "print('Reservations:')\n",
    "print('---------------')\n",
    "print(f'{weber.list_reservations()}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "Y0CyKooF2k0_"
   },
   "source": [
    "The processor also comes with a stock calibration metric report. By default, all of the error values are zero."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "an5RBcbK2jNs"
   },
   "outputs": [],
   "source": [
    "print('Calibrations:')\n",
    "print('---------------')\n",
    "calibration = weber.list_calibrations()[0]\n",
    "print(f\"Calibration metrics: \\n    {list(calibration.keys())}\")\n",
    "# Example calibration data\n",
    "for metric in [\"single_qubit_p00_error\", \"two_qubit_sycamore_gate_xeb_average_error_per_cycle\"]:\n",
    "    print(metric)\n",
    "    data = calibration[metric]\n",
    "    # Only print the first couple qubits/qubit pairs\n",
    "    for key in list(data.keys())[:3]:\n",
    "        print(f'   {key}: {data[key]}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "XFsv1dkrQqIs"
   },
   "source": [
    "## Create a custom processor from a device\n",
    "\n",
    "You can also create processors to mimic other devices as needed.  Each of these classes is customizable and can be modified to suit your simulation needs.\n",
    "\n",
    "You can create processors from existing devices, like `cirq_google.Sycamore`, with `cirq_google.engine.create_noiseless_virtual_engine_from_device`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "TBdZgcyGRFKy"
   },
   "outputs": [],
   "source": [
    "sycamore_engine = cirq_google.engine.create_noiseless_virtual_engine_from_device(\n",
    "    'sycamore', cirq_google.Sycamore\n",
    ")\n",
    "\n",
    "# Note that the previous function creates an engine with just one processor\n",
    "print([proc.processor_id for proc in sycamore_engine.list_processors()])\n",
    "print(sycamore_engine.get_processor('sycamore').get_device())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "n0CXvI0XR9iv"
   },
   "source": [
    "## Create a custom processor from a specification\n",
    "\n",
    "You can also create virtual engines from device specifications written in the [Protocol Buffer](https://developers.google.com/protocol-buffers) structured-data file format. This allows for detailed custom device creation, in the case where you want to see how a slightly modified existing device, or a completely new device, would work in Cirq.\n",
    "\n",
    "The previous specification files mentioned in the [devices/specifications](https://github.com/quantumlib/Cirq/tree/main/cirq-google/cirq_google/devices/specifications) in the Cirq repository are already in this file format. The details of this format are subject to change as Cirq is updated, but it is designed to be human-readable. If you want to work with a very custom device, the best place to start is by inspecting one of these files, but be aware that the format may change without notice."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "WS8vRtvMRxBj"
   },
   "outputs": [],
   "source": [
    "import importlib\n",
    "from cirq_google.devices import specifications\n",
    "\n",
    "# Get the processor identifier and file location from MOST_RECENT_TEMPLATES.\n",
    "processor_id, template_name = next(\n",
    "    iter(cirq_google.engine.virtual_engine_factory.MOST_RECENT_TEMPLATES.items())\n",
    ")\n",
    "# Read the protobuf template.\n",
    "device_str = importlib.resources.files(specifications).joinpath(template_name).read_text()\n",
    "# Print just the first 10 lines of the very long protobuf specification.\n",
    "print(f'Processor: {processor_id}')\n",
    "print('\\n'.join(device_str.splitlines()[:10]))\n",
    "print('...')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "MoPt-4l3B6-x"
   },
   "source": [
    "In order to use this specification protobuf file string, parse it with `google.protobuf.text_format` and create the `SimulatedLocalEngine` with `cirq_google.engine.create_noiseless_virtual_engine_from_proto`. \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "0Ix-jMPvB5qV"
   },
   "outputs": [],
   "source": [
    "import google.protobuf.text_format as text_format\n",
    "\n",
    "# Import the spec.\n",
    "device_spec = cirq_google.api.v2.device_pb2.DeviceSpecification()\n",
    "text_format.Parse(device_str, device_spec)\n",
    "four_engine = cirq_google.engine.create_noiseless_virtual_engine_from_proto(\n",
    "    processor_id, device_spec\n",
    ")\n",
    "# Prepare a sampler.\n",
    "print([proc.processor_id for proc in four_engine.list_processors()])\n",
    "processor = four_engine.get_processor(processor_id)\n",
    "print(processor.get_device())\n",
    "sampler = processor.get_sampler()\n",
    "\n",
    "q1_1 = cirq.GridQubit(1, 1)\n",
    "q1_2 = cirq.GridQubit(1, 2)\n",
    "q2_1 = cirq.GridQubit(2, 1)\n",
    "# Run a circuit with one each of Z, CZ, Measure, and CircuitOperation.\n",
    "circuit = cirq.Circuit(\n",
    "    cirq.CircuitOperation(cirq.FrozenCircuit(cirq.Z(q2_1), cirq.CZ(q1_1, q1_2))),\n",
    "    cirq.measure(q1_1),\n",
    "    cirq.measure(q2_1),\n",
    ")\n",
    "print('results', '\\n')\n",
    "try:\n",
    "    print(sampler.run(circuit))\n",
    "except ValueError as e:\n",
    "    print(e)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "zOtDbCSas8qm"
   },
   "source": [
    "# Summary\n",
    "\n",
    "Cirq provides the `cirq.SimulatedLocalEngine`. which allows you to run circuits on the Cirq Simulator through the same interface as the `cirq.Engine` object, which is used for running on real quantum hardware. This is useful both as a preparation step before running on real quantum hardware, and as a substitute when real hardware is unavailable.\n",
    "\n",
    "As presented in this page, the virtual Engine is completely noiseless. In order to learn about using the virtual Engine with noise models, including realistic noise models which closely mimic actual hardware, see the [Quantum Virtual Machine](./quantum_virtual_machine.ipynb) page. "
   ]
  }
 ],
 "metadata": {
  "colab": {
   "collapsed_sections": [],
   "name": "virtual_engine_interface.ipynb",
   "toc_visible": true
  },
  "kernelspec": {
   "display_name": "Python 3",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
